﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using GMusicUtil.Common.Commands;
using NLog;

namespace GMusicUtil.Common.ViewModels
{
    public class LoaderViewModel : BaseViewModel
    {
        private static readonly Logger Log = LogManager.GetCurrentClassLogger();

        public LoaderViewModel()
        {
            ProcessStatus = "Load Files and Database to Process...";
        }

        #region Mp3Directory

        private string propMp3Directory = default(string);

        public const string Mp3DirectoryPropertyName = "Mp3Directory";

        private static readonly PropertyChangedEventArgs Mp3DirectoryChangedEventArgs = new PropertyChangedEventArgs(Mp3DirectoryPropertyName);

        public string Mp3Directory
        {
            get { return propMp3Directory; }
            set
            {
                if (propMp3Directory != value)
                {
                    propMp3Directory = value.Trim();

                    OnMp3DirectoryChanged();
                }
            }
        }

        protected virtual void OnMp3DirectoryChanged()
        {
            OnPropertyChanged(Mp3DirectoryChangedEventArgs);

            RunOnUiThread(GetLoadFilesCommand().RaiseCanExecuteChanged);
        }

        #endregion

        #region DatabasePath

        private string propDatabasePath = default(string);

        public const string DatabasePathPropertyName = "DatabasePath";

        private static readonly PropertyChangedEventArgs DatabasePathChangedEventArgs = new PropertyChangedEventArgs(DatabasePathPropertyName);

        public string DatabasePath
        {
            get { return propDatabasePath; }
            set
            {
                if (propDatabasePath != value)
                {
                    propDatabasePath = value.Trim();

                    OnDatabasePathChanged();
                }
            }
        }

        protected virtual void OnDatabasePathChanged()
        {
            OnPropertyChanged(DatabasePathChangedEventArgs);

            RunOnUiThread(GetLoadEntitiesCommand().RaiseCanExecuteChanged);
        }

        #endregion

        #region Mp3Files

        private ObservableCollection<FileInfo> propMp3Files = default(ObservableCollection<FileInfo>);

        public const string Mp3FilesPropertyName = "Mp3Files";

        private static readonly PropertyChangedEventArgs Mp3FilesChangedEventArgs = new PropertyChangedEventArgs(Mp3FilesPropertyName);

        public ObservableCollection<FileInfo> Mp3Files
        {
            get { return propMp3Files; }
            set
            {
                if (propMp3Files != value)
                {
                    propMp3Files = value;

                    OnMp3FilesChanged();
                }
            }
        }

        protected virtual void OnMp3FilesChanged()
        {
            OnPropertyChanged(Mp3FilesChangedEventArgs);

            ProcessStatus = string.Format("Load Database to Process {0} Files...", Mp3Files.Count);
        }

        #endregion

        #region DatabaseEntities

        private ObservableCollection<Data.MUSIC> propDatabaseEntities = default(ObservableCollection<Data.MUSIC>);

        public const string DatabaseEntitiesPropertyName = "DatabaseEntities";

        private static readonly PropertyChangedEventArgs DatabaseEntitiesChangedEventArgs = new PropertyChangedEventArgs(DatabaseEntitiesPropertyName);

        public ObservableCollection<Data.MUSIC> DatabaseEntities
        {
            get { return propDatabaseEntities; }
            set
            {
                if (propDatabaseEntities != value)
                {
                    propDatabaseEntities = value;

                    OnDatabaseEntitiesChanged();
                }
            }
        }

        protected virtual void OnDatabaseEntitiesChanged()
        {
            OnPropertyChanged(DatabaseEntitiesChangedEventArgs);
        }

        #endregion

        #region FileLoadProgress

        private double propFileLoadProgress = default(double);

        public const string FileLoadProgressPropertyName = "FileLoadProgress";

        private static readonly PropertyChangedEventArgs FileLoadProgressChangedEventArgs = new PropertyChangedEventArgs(FileLoadProgressPropertyName);

        public double FileLoadProgress
        {
            get { return propFileLoadProgress; }
            set
            {
                if (propFileLoadProgress != value)
                {
                    propFileLoadProgress = value;

                    OnFileLoadProgressChanged();
                }
            }
        }

        protected virtual void OnFileLoadProgressChanged()
        {
            OnPropertyChanged(FileLoadProgressChangedEventArgs);

            RunOnUiThread(GetUpdateFilesCommand().RaiseCanExecuteChanged);
        }

        #endregion

        #region FileLoadStatus

        private string propFileLoadStatus = default(string);

        public const string FileLoadStatusPropertyName = "FileLoadStatus";

        private static readonly PropertyChangedEventArgs FileLoadStatusChangedEventArgs = new PropertyChangedEventArgs(FileLoadStatusPropertyName);

        public string FileLoadStatus
        {
            get { return propFileLoadStatus; }
            set
            {
                if (propFileLoadStatus != value)
                {
                    propFileLoadStatus = value;

                    OnFileLoadStatusChanged();
                }
            }
        }

        protected virtual void OnFileLoadStatusChanged()
        {
            OnPropertyChanged(FileLoadStatusChangedEventArgs);

            LogFormat(FileLoadStatus);
        }

        #endregion

        #region EntityLoadProgress

        private double propEntityLoadProgress = default(double);

        public const string EntityLoadProgressPropertyName = "EntityLoadProgress";

        private static readonly PropertyChangedEventArgs EntityLoadProgressChangedEventArgs = new PropertyChangedEventArgs(EntityLoadProgressPropertyName);

        public double EntityLoadProgress
        {
            get { return propEntityLoadProgress; }
            set
            {
                if (propEntityLoadProgress != value)
                {
                    propEntityLoadProgress = value;

                    OnEntityLoadProgressChanged();
                }
            }
        }

        protected virtual void OnEntityLoadProgressChanged()
        {
            OnPropertyChanged(EntityLoadProgressChangedEventArgs);

            RunOnUiThread(GetUpdateFilesCommand().RaiseCanExecuteChanged);
        }

        #endregion

        #region EntityLoadStatus

        private string propEntityLoadStatus = default(string);

        public const string EntityLoadStatusPropertyName = "EntityLoadStatus";

        private static readonly PropertyChangedEventArgs EntityLoadStatusChangedEventArgs = new PropertyChangedEventArgs(EntityLoadStatusPropertyName);

        public string EntityLoadStatus
        {
            get { return propEntityLoadStatus; }
            set
            {
                if (propEntityLoadStatus != value)
                {
                    propEntityLoadStatus = value;

                    OnEntityLoadStatusChanged();
                }
            }
        }

        protected virtual void OnEntityLoadStatusChanged()
        {
            OnPropertyChanged(EntityLoadStatusChangedEventArgs);

            LogFormat(EntityLoadStatus);
        }

        #endregion

        #region ProcessProgress

        private double propProcessProgress = default(double);

        public const string ProcessProgressPropertyName = "ProcessProgress";

        private static readonly PropertyChangedEventArgs ProcessProgressChangedEventArgs = new PropertyChangedEventArgs(ProcessProgressPropertyName);

        public double ProcessProgress
        {
            get { return propProcessProgress; }
            set
            {
                if (propProcessProgress != value)
                {
                    propProcessProgress = value;

                    OnProcessProgressChanged();
                }
            }
        }

        protected virtual void OnProcessProgressChanged()
        {
            OnPropertyChanged(ProcessProgressChangedEventArgs);
        }

        #endregion

        #region ProcessStatus

        private string propProcessStatus = default(string);

        public const string ProcessStatusPropertyName = "ProcessStatus";

        private static readonly PropertyChangedEventArgs ProcessStatusChangedEventArgs = new PropertyChangedEventArgs(ProcessStatusPropertyName);

        public string ProcessStatus
        {
            get { return propProcessStatus; }
            set
            {
                if (propProcessStatus != value)
                {
                    propProcessStatus = value;

                    OnProcessStatusChanged();
                }
            }
        }

        protected virtual void OnProcessStatusChanged()
        {
            OnPropertyChanged(ProcessStatusChangedEventArgs);
        }

        #endregion

        #region IsProcessingFiles

        private bool propIsProcessingFiles = default(bool);

        public const string IsProcessingFilesPropertyName = "IsProcessingFiles";

        private static readonly PropertyChangedEventArgs IsProcessingFilesChangedEventArgs = new PropertyChangedEventArgs(IsProcessingFilesPropertyName);

        public bool IsProcessingFiles
        {
            get { return propIsProcessingFiles; }
            set
            {
                if (propIsProcessingFiles != value)
                {
                    propIsProcessingFiles = value;

                    OnIsProcessingFilesChanged();
                }
            }
        }

        protected virtual void OnIsProcessingFilesChanged()
        {
            OnPropertyChanged(IsProcessingFilesChangedEventArgs);
        }

        #endregion

        #region LogText

        private StringBuilder propLogText = new StringBuilder();

        public const string LogTextPropertyName = "LogText";

        private static readonly PropertyChangedEventArgs LogTextChangedEventArgs = new PropertyChangedEventArgs(LogTextPropertyName);

        public string LogText
        {
            get { return propLogText.ToString(); }
            set
            {
                propLogText.AppendLine(value);
                OnLogTextChanged();
            }
        }

        protected virtual void OnLogTextChanged()
        {
            OnPropertyChanged(LogTextChangedEventArgs);
        }

        #endregion

        #region LoadFiles Command

        private DelegateCommand propLoadFilesCommand = null;

        public ICommand LoadFilesCommand
        {
            get
            {
                return GetLoadFilesCommand();
            }
        }

        private DelegateCommand GetLoadFilesCommand()
        {
            if (propLoadFilesCommand == null)
            {
                propLoadFilesCommand = new DelegateCommand(ExecuteLoadFilesCommand, CanExecuteLoadFilesCommand);
            }
            return propLoadFilesCommand;
        }

        private bool CanExecuteLoadFilesCommand()
        {
            var canExecute = false;

            if (string.IsNullOrEmpty(Mp3Directory) == false)
            {
                var dir = new DirectoryInfo(Mp3Directory);

                canExecute = dir.Exists;
            }

            return canExecute;
        }

        private void ExecuteLoadFilesCommand()
        {
            RunOffUiThread(LoadFiles);
        }

        #endregion

        #region LoadEntities Command

        private DelegateCommand propLoadEntitiesCommand = null;

        public ICommand LoadEntitiesCommand
        {
            get
            {
                return GetLoadEntitiesCommand();
            }
        }

        private DelegateCommand GetLoadEntitiesCommand()
        {
            if (propLoadEntitiesCommand == null)
            {
                propLoadEntitiesCommand = new DelegateCommand(ExecuteLoadEntitiesCommand, CanExecuteLoadEntitiesCommand);
            }
            return propLoadEntitiesCommand;
        }

        private bool CanExecuteLoadEntitiesCommand()
        {
            var canExecute = false;

            if (string.IsNullOrEmpty(DatabasePath) == false)
            {
                var file = new FileInfo(DatabasePath);

                if (file.Exists)
                {
                    try
                    {
                        using (var ctx = new Data.MusicEntities(DatabasePath))
                        {
                            canExecute = true;
                        }
                    }
                    catch (Exception e)
                    {
                        Log.WarnException("Unable to load data context", e);
                        LogText = e.Message;
                    }
                }
            }

            return canExecute;
        }

        private void ExecuteLoadEntitiesCommand()
        {
            RunOffUiThread(LoadEntities);
        }

        #endregion

        #region UpdateFiles Command

        private DelegateCommand propUpdateFilesCommand = null;

        public ICommand UpdateFilesCommand
        {
            get
            {
                return GetUpdateFilesCommand();
            }
        }

        private DelegateCommand GetUpdateFilesCommand()
        {
            if (propUpdateFilesCommand == null)
            {
                propUpdateFilesCommand = new DelegateCommand(ExecuteUpdateFilesCommand, CanExecuteUpdateFilesCommand);
            }
            return propUpdateFilesCommand;
        }

        private bool CanExecuteUpdateFilesCommand()
        {
            var canExecute = FileLoadProgress == 1 && EntityLoadProgress == 1;

            if (canExecute)
            {
                ProcessStatus = string.Format("Process {0} Files", Mp3Files.Count);
            }

            return canExecute;
        }

        private void ExecuteUpdateFilesCommand()
        {
            RunOffUiThread(ProcessFiles);
        }

        #endregion

        #region Functions

        private void LogFormat(string format, params object[] args)
        {
            Log.Debug(format, args);

            LogText = string.Format(format, args);
        }

        private void LoadFiles()
        {
            LogFormat("Loading Files From {0}", Mp3Directory);

            var dir = new DirectoryInfo(Mp3Directory);

            Mp3Files = new ObservableCollection<FileInfo>();
            FileLoadProgress = 0;
            FileLoadStatus = "Scanning for Files...";

            var files = dir.EnumerateFiles("*.mp3", SearchOption.AllDirectories);
            double count = files.Count();

            var i = 0;
            foreach (var file in files)
            {
                Mp3Files.Add(file);

                FileLoadStatus = string.Format("{0} / {1} Loaded", ++i, count);
                FileLoadProgress = i / count;
            }

            LogFormat("File Loading Complete.");
        }

        private void LoadEntities()
        {
            LogFormat("Loading Entities From {0}", DatabasePath);

            HashSet<long> ids = null;
            if (Mp3Files != null && Mp3Files.Count > 0)
            {
                ids = new HashSet<long>(Mp3Files.Select(x => Convert.ToInt64(x.Name.Substring(0, x.Name.Length - x.Extension.Length))));
            }

            DatabaseEntities = new ObservableCollection<Data.MUSIC>();
            EntityLoadProgress = 0;
            EntityLoadStatus = "Scanning for Entities...";

            using (var ctx = new Data.MusicEntities(DatabasePath))
            {
                var entities = ctx.MUSIC;
                double count = entities.Count();

                var i = 0;
                foreach (var entity in entities)
                {
                    if (ids != null && ids.Contains(entity.Id) == false)
                    {
                        ++i;
                        continue;
                    }

                    DatabaseEntities.Add(entity);

                    EntityLoadStatus = string.Format("{0} / {1} Loaded", ++i, count);
                    EntityLoadProgress = i / count;
                }
            }

            LogFormat("Entity Loading Complete.");
        }

        private void ProcessFiles()
        {
            LogFormat("Processing Files");

            ProcessProgress = 0;
            IsProcessingFiles = true;

            double count = Mp3Files.Count;

            var i = 0;
            foreach (var item in Mp3Files.Join(DatabaseEntities, x => x.Name.Substring(0, x.Name.Length - x.Extension.Length), x => x.Id.ToString(), (File, Entity) => new { File, Entity }))
            {
                var newFileName = string.Format("{0} - {1}{2}", item.Entity.Artist, item.Entity.Title, item.File.Extension);

                LogFormat("Renaming {0} to {1}", item.File.Name, newFileName);

                item.File.MoveTo(Path.Combine(item.File.Directory.FullName, newFileName));

                ProcessStatus = string.Format("{0} / {1} Processed", ++i, Mp3Files.Count);
                ProcessProgress = i / count;
            }

            IsProcessingFiles = false;

            LogFormat("Processing Files");
        }

        #endregion
    }
}
